package cn.zooz.admin.config.shiro;

import cn.zooz.admin.config.shiro.cache.ShiroRedisCacheManager;
import cn.zooz.admin.config.shiro.filter.KickoutSessionControlFilter;
import cn.zooz.admin.config.shiro.listener.KickOutAuthenticationListener;
import cn.zooz.admin.config.shiro.session.RedisSessionDao;
import cn.zooz.admin.config.shiro.session.ZoozWebSessionManager;
import cn.zooz.admin.config.shiro.tag.PlatformFreeMarkerConfigurer;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationListener;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.assertj.core.util.Sets;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by Bradish7Y on 2017/9/8.
 */
@Configuration
@Slf4j
public class ShiroConfig {

    @Value("${shiro.session.expireTime:1800000}")
    private int sessionExpireTime;

    @Value("${shiro.cache.expireTime:900}")
    private int cacheExpireTime;

    @Value("${shiro.session.keyPrefix}")
    private String sessionKeyPrefix;

    @Value("${shiro.session.maxSession}")
    private int maxSession;

    @Value("${shiro.session.kickout.enable:false}")
    private boolean kickOutEnable;

    @Value("${shiro.session.kickout.url}")
    private String kickOutUrl;

    @Value("${shiro.crypto.hashAlgorithmName}")
    private String hashAlgorithmName;

    @Value("${shiro.crypto.hashIterations}")
    private int hashIterations;

    @Value("${shiro.crypto.storedCredentialsHexEncoded}")
    private boolean storedCredentialsHexEncoded;

    @Value("${shiro.cookie.remeberme.maxAge}")
    private int remebermeMaxAge;

    @Value("${shiro.cookie.remeberme.httpOnly}")
    private boolean remebermeHttpOnly;

    @Value("${shiro.cookie.remeberme.name}")
    private String remebermeName;

    @Value("${shiro.cookie.session.name}")
    private String sessionCookieName;

    @Value("${shiro.cookie.session.maxAge}")
    private int sessionCookieMaxAge;

    @Value("${shiro.cookie.session.httpOnly:true}")
    private boolean sessionCookieHttpOnly;

    /**
     * freeMarker support shiro tag
     */
    @Bean
    public PlatformFreeMarkerConfigurer platformFreeMarkerConfigurer() {
        return new PlatformFreeMarkerConfigurer();
    }

    /**
     * shiro缓存cacheManager
     */
    @Bean
    public CacheManager shiroCacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager manager = new RedisCacheManager(redisTemplate);
        manager.setDefaultExpiration(cacheExpireTime);//s
        manager.setUsePrefix(false);
        return manager;
    }

    /**
     * shiro cache manager
     */
    @Bean
    public ShiroRedisCacheManager shiroRedisCacheManager(@Qualifier("shiroCacheManager") CacheManager shiroCacheManager) {
        log.info("load shiroRedisCacheManager.....");
        ShiroRedisCacheManager shiroRedisCacheManager = new ShiroRedisCacheManager();
        shiroRedisCacheManager.setCacheManager(shiroCacheManager);

        return shiroRedisCacheManager;
    }

    /**
     * **********session*******************
     */
    @Bean
    public RedisSessionDao sessionDAO() {
        log.info("load sessionDAO.....");
        RedisSessionDao sessionDAO = new RedisSessionDao();
        sessionDAO.setExpireTime(sessionExpireTime);
        sessionDAO.setKeyPrefix(sessionKeyPrefix);
        return sessionDAO;
    }

    @Bean
    public Cookie sessionCookie() {
        SimpleCookie cookie = new SimpleCookie();
        cookie.setName(sessionCookieName);
        cookie.setMaxAge(sessionCookieMaxAge);
        cookie.setHttpOnly(sessionCookieHttpOnly);
        return cookie;
    }

    //会话验证调度器, 时间单位是毫秒
//    @Bean
//    public SessionValidationScheduler sessionValidationScheduler() {
//        log.info("load sessionValidationScheduler.....");
//        QuartzSessionValidationScheduler task = new QuartzSessionValidationScheduler();
//        task.setSessionValidationInterval(sessionExpireTime);//ms
//        task.setSessionManager(sessionManager());
//
//        return task;
//    }

    @Bean
    public ZoozWebSessionManager sessionManager() {
        log.info("load sessionManager.....");
        ZoozWebSessionManager sessionManager = new ZoozWebSessionManager();
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setGlobalSessionTimeout(sessionExpireTime);
        sessionManager.setDeleteInvalidSessions(true);//超时自动删除
        /**
         * 去掉url中的sessionId
         * 默认参数名为jsessionid，如果需要修改，参考：http://alex233.blog.51cto.com/blog/8904951/1856155
         */
        sessionManager.setSessionIdUrlRewritingEnabled(false);

        //sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(sessionCookie());

        return sessionManager;
    }

    /**
     * **********cookieession*******************
     */
    @Bean
    public Cookie remeberMeCookie() {
        SimpleCookie cookie = new SimpleCookie();
        cookie.setName(remebermeName);
        cookie.setMaxAge(remebermeMaxAge);
        cookie.setHttpOnly(remebermeHttpOnly);
        return cookie;
    }

    @Bean
    public RememberMeManager cookieRememberMeManager() {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCookie(remeberMeCookie());
        //manager.setCipherKey();
        return manager;

    }

    @Bean
    public AuthenticationListener kickOutAuthenticationListener() {
        KickOutAuthenticationListener listener = new KickOutAuthenticationListener();
        listener.setKickout(kickOutEnable);
        listener.setSessionExpire(sessionExpireTime / 1000);
        listener.setMaxSession(maxSession);
        listener.setSessionManager(sessionManager());
        return listener;
    }

    /**
     * securityManager
     */
    @Bean
    public SecurityManager securityManager(ShiroRedisCacheManager shiroRedisCacheManager) {
        log.info("load securityManager.....");
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());

        //sessionManager().setSessionValidationScheduler(sessionValidationScheduler());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(shiroRedisCacheManager);
        securityManager.setRememberMeManager(cookieRememberMeManager());

        /**
         * 增加验证器监听器
         */
        Set<AuthenticationListener> authenticationListenerSet = Sets.newHashSet();
        authenticationListenerSet.add(kickOutAuthenticationListener());
        ModularRealmAuthenticator m = (ModularRealmAuthenticator) securityManager.getAuthenticator();
        m.setAuthenticationListeners(authenticationListenerSet);

        return securityManager;
    }

    @Bean
    public KickoutSessionControlFilter kickOutSessionControlFilter() {
        KickoutSessionControlFilter filter = new KickoutSessionControlFilter();
        filter.setKickOutUrl(kickOutUrl);
        filter.setEnable(kickOutEnable);
        return filter;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        log.info("load shiroFilter.....");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        /**
         * filter
         */
        Map<String, Filter> filterMap = Maps.newHashMap();
        filterMap.put("kickout", kickOutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/logout", "anon");
        filterChainDefinitionMap.put("/reg", "anon");
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/index", "anon");

        filterChainDefinitionMap.put("/add", "perms[权限添加]");//添加权限

        filterChainDefinitionMap.put("/user", "authc");
        filterChainDefinitionMap.put("/**", "kickout,user");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    public FilterRegistrationBean delegatingFilterProxy() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }

    /**
     * 密码加密
     *
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        log.info("load hashedCredentialsMatcher.....");

        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        credentialsMatcher.setHashIterations(hashIterations);
        credentialsMatcher.setStoredCredentialsHexEncoded(storedCredentialsHexEncoded);
        return credentialsMatcher;
    }

    /**
     * 自定义realm
     */
    @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ZoozShiroRealm shiroRealm() {
        log.info("load shiroRealm.....");

        ZoozShiroRealm realm = new ZoozShiroRealm();
        realm.setCachingEnabled(true);
        realm.setAuthenticationCachingEnabled(false);
        realm.setAuthorizationCachingEnabled(true);
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        log.info("load authorizationAttributeSourceAdvisor.....");

        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }


}
